package io.yx.flx.service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import cn.hutool.json.JSONObject;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.ObjectMetadata;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.ext.web.FileUpload;
import io.vertx.ext.web.RoutingContext;
import io.yx.flx.Main;
import io.yx.flx.entity.BaseResponse;
import io.yx.flx.entity.FileInfo;
import io.yx.flx.entity.TokenEntity;
import io.yx.flx.enums.CompressEnum;
import net.coobird.thumbnailator.Thumbnails;
import nu.pattern.OpenCV;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.security.Security;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 文件上传实现
 */
public class UploadServiceImpl implements UploadService {

    private static final Log log = LogFactory.get();


    private static final String endpoint = System.getProperty("oss.endpoint");
    private static final String accessKeyId = System.getProperty("oss.accessKeyId");
    private static final String accessKeySecret = System.getProperty("oss.accessKeySecret");
    private static final String bucketName = System.getProperty("oss.bucketName");
    private static final String folder = System.getProperty("oss.folder");
    private static final String folderCompress = System.getProperty("oss.folderCompress");

    private static boolean canUseOpenCV = false;

    private static OSS oss = null;

    static {
        if (Main.setting.getBool("openOSS")) {
            oss = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        }

        // 添加加密算法提供者maven：bcprov-jdk15on，解决oss报错Unsupported algorithm: HmacSHA1
        Security.addProvider(new BouncyCastleProvider());
        ImageIO.scanForPlugins();// 提前使用ImageIO,避免出现初始化错误
        try {// opencv加载JNI配置
            OpenCV.loadShared();
            canUseOpenCV = true;
        } catch (Throwable e) {
            canUseOpenCV = false;
            // 无法使用opencv压缩,改为使用thumbnails
            Main.compressMethod = CompressEnum.thumbnails;
            log.error("加载OpenCV失败,将无法使用OpenCV进行压缩", e);
        }
    }


    @Override
    public String uploadReturnName(RoutingContext rc) {
        HttpServerRequest req = rc.request();

        try {
            String token = req.getParam("token");
            if (StrUtil.isBlank(token)) {
                return BaseResponse.error("token is null");
            }
            List<TokenEntity> entityList = Db.use().find(Entity.create("token_entity").set("token", token), TokenEntity.class);
            if (entityList.isEmpty()) {
                return BaseResponse.error("token无效");
            }
            TokenEntity tokenEntity = entityList.stream().findAny().get();
            Boolean used = tokenEntity.getUsed();
            if (Boolean.TRUE.equals(used)) {
                return BaseResponse.error("token已被使用");
            }

            List<JSONObject> urls = new ArrayList<>();

            List<FileUpload> files = rc.fileUploads();

            files.parallelStream().forEach(file->{
                String fileName = file.fileName();
                String suffixName = FileUtil.getSuffix(fileName);
                String uploadedFileName = file.uploadedFileName();

                // 源路径
                String name = Main.getBasePath() + File.separator + uploadedFileName;

                if (StrUtil.isNotBlank(StrUtil.subAfter(uploadedFileName, File.separator, true))) {
                    uploadedFileName = StrUtil.subAfter(uploadedFileName, File.separator, true);
                }
                // 重命名文件后缀
                File renameFile = FileUtil.rename(new File(name), uploadedFileName + "." + suffixName, true);

                String host = req.host();
                String protocol = req.isSSL() ? "https" : "http";
                String flxUrl = protocol + "://" + host + "/storage/" + uploadedFileName + "." + suffixName;

                JSONObject fileInfo = new JSONObject();

                // 调用oss文件上传
                if (Boolean.parseBoolean(Main.setting.getStr("openOSS"))) {
                    FileInfo ossFileInfo = this.ossUpload(renameFile, true);
                    if (ossFileInfo == null) {
                        throw new RuntimeException("上传oss失败，文件信息为空");
                    }
                    fileInfo.set("url", ossFileInfo.getUrl());
                }

                fileInfo.set("fileName", fileName);
                fileInfo.set("flxUrl", flxUrl);
                urls.add(fileInfo);
            });

            Db.use().update(Entity.create("token_entity").set("used", true), Entity.create("token_entity").set("token", token));

            // 原结构为如果是单文件则直接返回url，如果是多文件则返回url+文件信息数组

            return BaseResponse.success(urls);
        } catch (Exception e) {
            log.error(e);
            return BaseResponse.error(e.getMessage());
        }
    }

    @Override
    public FileInfo ossUpload(File file, boolean compress) {

        //创建每月的文件夹
        String final_folder = createFolder(oss, bucketName, folder + "/" + DateUtil.format(new Date(), "yyyy-MM"));
        String targetFileName = createFileName() + "." + FileUtil.getSuffix(file);

        ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentLength(file.length());
        metadata.setContentType(getContentType(file.getName()));
        metadata.setContentEncoding("utf-8");
        metadata.setContentDisposition("filename/filesize=" + targetFileName + "/" + file.length() + "Byte.");

        // 上传文件
        oss.putObject(bucketName, final_folder + targetFileName, file, metadata);
        // 获得最后的url
        String url = getUrl(oss, final_folder, targetFileName);

        // 判断是否为图片文件，如果是图片文件，则尝试上传一份压缩版本
        if (isImg(FileUtil.getSuffix(file)) && compress) {
            // 判断文件宽度是否超过1000，压缩规则为如果宽度超过1000则压缩一份
            try {
                int width = 0;
                if (canUseOpenCV) {
                    Mat mat = Imgcodecs.imread(file.getAbsolutePath());
                    width = mat.width();
                } else {
                    BufferedImage bufferedImage = ImgUtil.read(file);
                    width = bufferedImage.getWidth();
                }
                if (width > 1200) {
                    // 压缩文件
                    compressFace(file, file, 1200);
                    // 创建压缩版的每月文件夹
                    String final_compress_folder = createFolder(oss, bucketName, folderCompress + "/" + DateUtil.format(new Date(), "yyyy-MM"));
                    //  设置元数据
                    metadata.setContentLength(file.length());
                    metadata.setContentDisposition("filename/filesize=" + targetFileName + "/" + file.length() + "Byte.");
                    // 上传到oss
                    oss.putObject(bucketName, final_compress_folder + targetFileName, file, metadata);
                    url = getUrl(oss, final_compress_folder, targetFileName);
                }
            } catch (Throwable e) {
                log.error("压缩图片失败，使用原图", e);
            }
        }

        FileInfo fileInfo = new FileInfo();
        fileInfo.setUrl(url);
        return fileInfo;
    }

    /**
     * 返回结果中不包含名称
     *
     * @param rc
     * @return
     */
    @Override
    public String uploadNoName(RoutingContext rc) {
        HttpServerRequest req = rc.request();

        try {
            String token = req.getParam("token");
            if (StrUtil.isBlank(token)) {
                return BaseResponse.error("token is null");
            }
            List<TokenEntity> entityList = Db.use().find(Entity.create("token_entity").set("token", token), TokenEntity.class);
            if (entityList.isEmpty()) {
                return BaseResponse.error("token无效");
            }
            TokenEntity tokenEntity = entityList.stream().findAny().get();
            Boolean used = tokenEntity.getUsed();
            if (Boolean.TRUE.equals(used)) {
                return BaseResponse.error("token已被使用");
            }

            List<String> urls = new ArrayList<>();

            List<FileUpload> files = rc.fileUploads();

            files.parallelStream().forEach(file -> {
                String fileName = file.fileName();
                String suffixName = FileUtil.getSuffix(fileName);
                String uploadedFileName = file.uploadedFileName();

                // 源路径
                String name = Main.getBasePath() + File.separator + uploadedFileName;

                File uploadSourceFile = new File(name);
                if (!uploadSourceFile.exists() || !uploadSourceFile.isFile() || uploadSourceFile.length() <= 0) {
                    return;
                }

                if (StrUtil.isNotBlank(StrUtil.subAfter(uploadedFileName, File.separator, true))) {
                    uploadedFileName = StrUtil.subAfter(uploadedFileName, File.separator, true);
                }
                // 重命名文件后缀
                File renameFile = FileUtil.rename(new File(name), uploadedFileName + "." + suffixName, true);

                String host = req.authority().host();
                String protocol = req.isSSL() ? "https" : "http";
                String flxUrl = protocol + "://" + host + "/storage/" + uploadedFileName + "." + suffixName;

                // 调用oss文件上传
                if (Boolean.parseBoolean(Main.setting.getStr("openOSS"))) {
                    FileInfo ossFileInfo = this.ossUpload(renameFile, true);
                    if (ossFileInfo == null) {
                        throw new RuntimeException("上传oss失败，文件信息为空");
                    }
                    urls.add(ossFileInfo.getUrl());
                } else {
                    urls.add(flxUrl);
                }
            });

            if (urls.isEmpty()) {
                return BaseResponse.error("请上传文件");
            }

            Db.use().update(Entity.create("token_entity").set("used", true), Entity.create("token_entity").set("token", token));
            if (urls.size() != 1) {// 如果只有一个则返回独立的url，否则返回数组
                return BaseResponse.success(urls);
            } else {
                return BaseResponse.success(urls.get(0));
            }
        } catch (Exception e) {
            log.error(e);
            return BaseResponse.error(e.getMessage());
        }
    }

    /**
     * 不压缩
     */
    @Override
    public String uploadNoCompress(RoutingContext rc) {
        HttpServerRequest req = rc.request();

        try {
            String token = req.getParam("token");
            if (StrUtil.isBlank(token)) {
                return BaseResponse.error("token is null");
            }
            List<TokenEntity> entityList = Db.use().find(Entity.create("token_entity").set("token", token), TokenEntity.class);
            if (entityList.isEmpty()) {
                return BaseResponse.error("token无效");
            }
            TokenEntity tokenEntity = entityList.stream().findAny().get();
            Boolean used = tokenEntity.getUsed();
            if (Boolean.TRUE.equals(used)) {
                return BaseResponse.error("token已被使用");
            }

            List<String> urls = new ArrayList<>();

            List<FileUpload> files = rc.fileUploads();

            files.parallelStream().forEach(file -> {
                String fileName = file.fileName();
                String suffixName = FileUtil.getSuffix(fileName);
                String uploadedFileName = file.uploadedFileName();

                // 源路径
                String name = Main.getBasePath() + File.separator + uploadedFileName;

                File uploadSourceFile = new File(name);
                if (!uploadSourceFile.exists() || !uploadSourceFile.isFile() || uploadSourceFile.length() <= 0) {
                    return;
                }

                if (StrUtil.isNotBlank(StrUtil.subAfter(uploadedFileName, File.separator, true))) {
                    uploadedFileName = StrUtil.subAfter(uploadedFileName, File.separator, true);
                }
                // 重命名文件后缀
                File renameFile = FileUtil.rename(new File(name), uploadedFileName + "." + suffixName, true);

                String host = req.host();
                String protocol = req.isSSL() ? "https" : "http";
                String flxUrl = protocol + "://" + host + "/storage/" + uploadedFileName + "." + suffixName;

                // 调用oss文件上传
                if (Boolean.parseBoolean(Main.setting.getStr("openOSS"))) {
                    FileInfo ossFileInfo = this.ossUpload(renameFile, false);
                    if (ossFileInfo == null) {
                        throw new RuntimeException("上传oss失败，文件信息为空");
                    }
                    urls.add(ossFileInfo.getUrl());
                } else {
                    urls.add(flxUrl);
                }
            });

            if (urls.size() == 0) {
                return BaseResponse.error("请上传文件");
            }

            Db.use().update(Entity.create("token_entity").set("used", true), Entity.create("token_entity").set("token", token));
            if (urls.size() != 1) {// 如果只有一个则返回独立的url，否则返回数组
                return BaseResponse.success(urls);
            } else {
                return BaseResponse.success(urls.get(0));
            }
        } catch (Exception e) {
            log.error(e);
            return BaseResponse.error(e.getMessage());
        }
    }


    public String createFolder(OSS ossClient, String bucketName, String folder) {

        //oss创建文件夹必须以/后缀，否则OSS Browser中不显示为文件夹，参考https://help.aliyun.com/document_detail/31910.html
        if (!folder.endsWith("/")) {
            folder += "/";
        }

        // 文件夹名
        final String keySuffixWithSlash = folder;

        // 判断文件夹是否存在，不存在则创建
        if (!ossClient.doesObjectExist(bucketName, keySuffixWithSlash)) {
            // 创建文件夹
            ossClient.putObject(bucketName, keySuffixWithSlash, new ByteArrayInputStream(new byte[0]));
            log.info("创建文件夹成功");
            // 得到文件夹名
            OSSObject object = ossClient.getObject(bucketName, keySuffixWithSlash);
            return object.getKey();
        }
        return keySuffixWithSlash;
    }

    public String createFileName() {
        return DateUtil.format(new Date(), "yyyyMMddHHmmss-sss") + "-" + RandomUtil.randomString(4);
    }

    /**
     * 通过文件名判断并获取OSS服务文件上传时文件的contentType
     *
     * @param fileName 文件名
     * @return 文件的contentType
     */
    public static String getContentType(String fileName) {
        // 文件的后缀名
        String fileExtension = fileName.substring(fileName.lastIndexOf("."));
        if (".bmp".equalsIgnoreCase(fileExtension)) {
            return "image/bmp";
        }
        if (".gif".equalsIgnoreCase(fileExtension)) {
            return "image/gif";
        }
        if (".jpeg".equalsIgnoreCase(fileExtension) || ".jpg".equalsIgnoreCase(fileExtension)
                || ".png".equalsIgnoreCase(fileExtension)) {
            return "image/jpeg";
        }
        if (".html".equalsIgnoreCase(fileExtension)) {
            return "text/html";
        }
        if (".txt".equalsIgnoreCase(fileExtension)) {
            return "text/plain";
        }
        if (".xml".equalsIgnoreCase(fileExtension)) {
            return "text/xml";
        }
        if (".vsd".equalsIgnoreCase(fileExtension)) {
            return "application/vnd.visio";
        }
        if (".ppt".equalsIgnoreCase(fileExtension) || ".pptx".equalsIgnoreCase(fileExtension)) {
            return "application/vnd.ms-powerpoint";
        }
        if (".doc".equalsIgnoreCase(fileExtension)) {
            return "application/msword";
        }
        if (".docx".equalsIgnoreCase(fileExtension)) {
            return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
        }
        if (".xls".equalsIgnoreCase(fileExtension)) {
            return "application/x-xls";
        }
        if (".xlsx".equalsIgnoreCase(fileExtension)) {
            return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
        }
        if (".pdf".equalsIgnoreCase(fileExtension)) {
            return "application/pdf";
        }
        if (".mp4".equalsIgnoreCase(fileExtension)) {
            return "video/mp4";
        }
        if (".zip".equalsIgnoreCase(fileExtension)) {
            return "application/zip";
        }
        // 默认返回类型
        return "application/octet-stream";
    }

    /**
     * 获得url链接
     */
    public String getUrl(OSS oss, String folder, String fileName) {
        // 设置URL过期时间为10年 3600l* 1000*24*365*10

        Date expiration = new Date(System.currentTimeMillis() + 3600L * 1000 * 24 * 365 * 10);

        //避免出现重复拼接图片地址
        String path = folder.endsWith("/") ? folder + fileName : folder + "/" + fileName;

        // 生成URL
        URL url = oss.generatePresignedUrl(bucketName, path, expiration);
        if (url != null) {

//            String result = url.toString().replace("test50.oss-cn-shanghai.aliyuncs.com", "cdnimg.gpai.net");
            String result = url.toString().replaceAll("test50.+\\.aliyuncs\\.com", "cdnimg.gpai.net");

            result = result.substring(0, result.indexOf("?Expires"));

            return result;
        } else {
            return null;
        }
    }

    /**
     * 压缩图片文件
     */
    public void compressImgByThumbnails(File file, File outputFile, int newWidth) throws IOException {
        Thumbnails.of(file).width(newWidth).outputQuality(0.9).toFile(outputFile);
    }

    /**
     * OpenCV压缩图片
     *
     * @param file
     * @param outputFile
     * @param newWidth
     * @throws IOException
     */
    public void compressImgByOpenCV(File file, File outputFile, int newWidth) throws IOException {
        Mat mat = Imgcodecs.imread(file.getAbsolutePath());
        // 计算等比缩放的高度
        float scale = (float) mat.width() / newWidth;
        int newHeight = (int) (mat.height() / scale);
        Mat newMat = new Mat();
        Imgproc.resize(mat, newMat, new Size(newWidth, newHeight));
        Imgcodecs.imwrite(outputFile.getAbsolutePath(), newMat);
    }

    public void compressFace(File file, File outputFile, int newWidth) throws IOException {
        if (CompressEnum.thumbnails.equals(Main.compressMethod)) {
            compressImgByThumbnails(file, outputFile, newWidth);
        } else {
            compressImgByOpenCV(file, outputFile, newWidth);
        }
    }


    /**
     * 根据文件名判断是否是图片，根据文件名后缀
     *
     * @param suffixName 文件后缀名
     */
    static boolean isImg(String suffixName) {
        if (suffixName == null) {
            throw new RuntimeException("文件名不能为空");
        }
        return "jpeg".equalsIgnoreCase(suffixName) || "jpg".equalsIgnoreCase(suffixName) || "png".equalsIgnoreCase(suffixName) || "gif".equalsIgnoreCase(suffixName) || "bmp".equalsIgnoreCase(suffixName);

    }
}
